﻿using AssemblyVerifier;
using EmitDebugging.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace EmitDebugging.Tests.Extensions
{
	[TestClass]
	public sealed class MethodBaseExtensionsGetArgumentTypesTests : CoreTests
	{
		private const string AssemblyName = "MethodBaseExtensionsGetArgumentTypesTests";
		private const string MethodDynamicMethodName = "DynamicMethod";
		private const string TypeName = "MethodBaseExtensionsGetArgumentTypes";

		private static MethodBuilder CreateMethod()
		{
			var name = new AssemblyName();
			name.Name = MethodBaseExtensionsGetArgumentTypesTests.AssemblyName;
			name.Version = new Version(1, 0, 0, 0);
			var fileName = name.Name + ".dll";

			var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
				 name, AssemblyBuilderAccess.Save);

			var moduleBuilder = assemblyBuilder.DefineDynamicModule(name.Name, fileName, false);

			var typeBuilder = MethodBaseExtensionsGetArgumentTypesTests.GenerateType(name, moduleBuilder);
			typeBuilder.GenerateConstructor();
			
			return MethodBaseExtensionsGetArgumentTypesTests.GenerateDynamicMethodMethod(typeBuilder);
		}

		private static TypeBuilder GenerateType(AssemblyName name, ModuleBuilder moduleBuilder)
		{
			var typeName = name.Name + "." + MethodBaseExtensionsGetArgumentTypesTests.TypeName;

			var typeBuilder = moduleBuilder.DefineType(
				 typeName, TypeAttributes.Class | TypeAttributes.Public,
				 typeof(object));
				 
			return typeBuilder;
		}

		private static MethodBuilder GenerateDynamicMethodMethod(TypeBuilder typeBuilder)
		{
			var methodBuilder = typeBuilder.DefineMethod(
				 MethodBaseExtensionsGetArgumentTypesTests.MethodDynamicMethodName,
				 MethodAttributes.Public | MethodAttributes.HideBySig |
					MethodAttributes.NewSlot | MethodAttributes.Virtual,
				null, new Type[] { typeof(string), typeof(Guid) });

			var methodGenerator = methodBuilder.GetILGenerator();
			methodGenerator.Emit(OpCodes.Ret);
			
			return methodBuilder;
		}

		[TestMethod]
		public void GetArgumentTypesForMethodBuilder()
		{
			Assert.AreEqual(0, 
				MethodBaseExtensionsGetArgumentTypesTests.CreateMethod().GetArgumentTypes().Length);
		}

		[TestMethod]
		public void GetArgumentTypesForMethodWithManyArguments()
		{
			var arguments = this.GetType().GetMethod("ManyArguments",
				BindingFlags.NonPublic | BindingFlags.Instance).GetArgumentTypes();
			Assert.AreEqual(4, arguments.Length);
			Assert.IsTrue(arguments.Contains(typeof(string)));
			Assert.IsTrue(arguments.Contains(typeof(Guid)));
			Assert.IsTrue(arguments.Contains(typeof(int)));
			Assert.IsTrue(arguments.Contains(typeof(object)));
		}

		[TestMethod]
		public void GetArgumentTypesForMethodWithNoArguments()
		{
			Assert.AreEqual(0, this.GetType().GetMethod("NoArguments",
				BindingFlags.NonPublic | BindingFlags.Instance).GetArgumentTypes().Length);
		}
		
		private void NoArguments()
		{
		}
		
		private void ManyArguments(string a, Guid b, int c, object d)
		{
		}
	}
}
